package com.scottmcfurry.psx.input;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;

import com.androidemu.Emulator;
import com.scottmcfurry.psx.R;
import com.scottmcfurry.psx.wrapper.Wrapper;

public class VirtualKeypad {

	private static final int DPAD_4WAY[] = {
		Emulator.GAMEPAD_LEFT,
		Emulator.GAMEPAD_UP,
		Emulator.GAMEPAD_RIGHT,
		Emulator.GAMEPAD_DOWN
	};//

	private static final int BUTTONS_4WAY[] = {
		Emulator.GAMEPAD_SQUARE,
		Emulator.GAMEPAD_TRIANGLE,
		Emulator.GAMEPAD_CIRCLE,
		Emulator.GAMEPAD_CROSS
	};//

//	private static final float DPAD_DEADZONE_VALUES[] = {
//		0.1f, 0.14f, 0.1667f, 0.2f, 0.25f,
//	};

	private Context context;//
	private View view;//
	private float scaleX;//
	private float scaleY;//
//	private int transparency;

	private GameKeyListener gameKeyListener;//
	private int keyStates;//
	private Vibrator vibrator;//
	private boolean vibratorEnabled;//
	private boolean dpad4Way;//
//	private float dpadDeadZone = DPAD_DEADZONE_VALUES[2];
//	private float pointSizeThreshold;
//	private boolean inBetweenPress;

	private ArrayList<Control> controls = new ArrayList<Control>();//
	private Control dpad;//
	private Control buttons;//
	private Control selectStart;//
	private Control leftShoulder;//
	private Control rightShoulder;//

	private Emulator emulator = Emulator.getInstance();//
	
	
	private Picture overlay;
	private Control touchedControls[] = new Control[4];

	public VirtualKeypad(View v, GameKeyListener l) {
		view = v;
		context = view.getContext();
		gameKeyListener = l;

		vibrator = (Vibrator) context.getSystemService(
				Context.VIBRATOR_SERVICE);

		dpad = createControl(R.drawable.dpad);
		buttons = createControl(R.drawable.buttons);
		selectStart = createControl(R.drawable.select_start_buttons);
		leftShoulder = createControl(R.drawable.tl_button_top);
		rightShoulder = createControl(R.drawable.tr_button_top);
	}

	public final int getKeyStates() {
		return keyStates;
	}

	public void reset() {
		keyStates = 0;
		int i = 0;
		while(i < touchedControls.length) {
			touchedControls[i]=null;
			i++;
		}
	}

	public final void destroy() {
		emulator.setOverlay(null);
	}

	public final void resize(int w, int h) {
		SharedPreferences prefs = PreferenceManager.
				getDefaultSharedPreferences(context);
		vibratorEnabled = prefs.getBoolean("enableVibrator", true);
		dpad4Way = prefs.getBoolean("dpad4Way", false);
		dpad.hide(prefs.getBoolean("hideDpad", false));
		buttons.hide(prefs.getBoolean("hideButtons", false));
		selectStart.hide(prefs.getBoolean("hideSelectStart", false));
		leftShoulder.hide(prefs.getBoolean("hideShoulders", false));
		rightShoulder.hide(prefs.getBoolean("hideShoulders", false));
		scaleX = (float)w/view.getWidth();
		scaleY = (float)h/view.getHeight();
		float controlScale = getControlScale(prefs);
		float sx = scaleX*controlScale;
		float sy = scaleY*controlScale;
		Resources res = context.getResources();
		for (Control c : controls) {
			c.load(res, sx, sy);
		}
		reposition(w, h, prefs);
		overlay = new Picture();
		draw(overlay.beginRecording(w, h), prefs);
		emulator.setOverlay(overlay);
	}

	private void draw(Canvas canvas, SharedPreferences prefs) {
		int transparency = prefs.getInt("vkeypadTransparency", 50);
		Paint paint = new Paint();
		paint.setAlpha(transparency * 2 + 30);

		for (Control c : controls)
			c.draw(canvas, paint);
	}

	private static float getControlScale(SharedPreferences prefs) {
		String value = prefs.getString("vkeypadSize", null);
		if ("small".equals(value))
			return 1.0f;
		if ("large".equals(value))
			return 1.33333f;
		return 1.2f;
	}

	private Control createControl(int resId) {
		Control c = new Control(resId);
		controls.add(c);
		return c;
	}

	private void makeBottomBottom(int w, int h) {
		if (dpad.getWidth() + buttons.getWidth() > w) {
			makeBottomTop(w, h);
			return;
		}

		dpad.move(0, h - dpad.getHeight());
		buttons.move(w - buttons.getWidth(), h - buttons.getHeight());
		leftShoulder.move(0, 0);
		rightShoulder.move(w - rightShoulder.getWidth(), 0);

		int x = (w + dpad.getWidth() - buttons.getWidth() -
				selectStart.getWidth()) / 2;
		if (x > dpad.getWidth())
			selectStart.move(x, h - selectStart.getHeight());
		else {
			x = (w - selectStart.getWidth()) / 2;
			int y = 0;
			if (x <= leftShoulder.getWidth()) {
				y += leftShoulder.getHeight();
			}
			selectStart.move(x, y);
		}
	}

	private void makeTopTop(int w, int h) {
		if (dpad.getWidth() + buttons.getWidth() > w) {
			makeBottomTop(w, h);
			return;
		}
		dpad.move(0, 0);
		buttons.move(w - buttons.getWidth(), 0);

		leftShoulder.reload(context.getResources(),
				R.drawable.tl_button_bottom);
		rightShoulder.reload(context.getResources(),
				R.drawable.tr_button_bottom);
		leftShoulder.move(0, h - leftShoulder.getHeight());
		rightShoulder.move(w - rightShoulder.getWidth(),
				h - rightShoulder.getHeight());
		
		int x = (w - selectStart.getWidth()) / 2;
		int y = h - selectStart.getHeight();
		if (x <= leftShoulder.getWidth()) {
			y += leftShoulder.getHeight();
		}
		selectStart.move(x, y);
	}

	private void makeTopBottom(int w, int h) {
		dpad.move(0, 0);
		buttons.move(w - buttons.getWidth(), h - buttons.getHeight());

		leftShoulder.reload(context.getResources(),
				R.drawable.tl_button_bottom);
		leftShoulder.move(0, h - leftShoulder.getHeight());
		rightShoulder.move(w - rightShoulder.getWidth(), 0);

		int x = (w + leftShoulder.getWidth() - buttons.getWidth() -
				selectStart.getWidth()) / 2;
		if (x > leftShoulder.getWidth())
			selectStart.move(x, h - selectStart.getHeight());
		else {
			x = (w + dpad.getWidth() - selectStart.getWidth()) / 2;
			selectStart.move(x, rightShoulder.getHeight());
		}
	}

	private void makeBottomTop(int w, int h) {
		dpad.move(0, h - dpad.getHeight());
		int y = 0;
		buttons.move(w - buttons.getWidth(), y);

		rightShoulder.reload(context.getResources(),
				R.drawable.tr_button_bottom);
		leftShoulder.move(0, 0);
		rightShoulder.move(w - rightShoulder.getWidth(),
				h - rightShoulder.getHeight());

		int x = (w + dpad.getWidth() - rightShoulder.getWidth() -
				selectStart.getWidth()) / 2;
		if (x > dpad.getWidth())
			selectStart.move(x, h - selectStart.getHeight());
		else {
			x = (w + dpad.getWidth() - selectStart.getWidth()) / 2;
			selectStart.move(x,
					h - leftShoulder.getHeight() - selectStart.getHeight());
		}
	}

	private void reposition(int w, int h, SharedPreferences prefs) {
		String layout = prefs.getString("vkeypadLayout", "top_bottom");

		if ("top_bottom".equals(layout))
			makeTopBottom(w, h);
		else if ("bottom_top".equals(layout))
			makeBottomTop(w, h);
		else if ("top_top".equals(layout))
			makeTopTop(w, h);
		else
			makeBottomBottom(w, h);
	}

	private boolean shouldVibrate(int oldStates, int newStates) {
		return (((oldStates ^ newStates) & newStates) != 0);
	}

	private void setKeyStates(int newStates) {
		if (keyStates == newStates)
			return;

		if (vibratorEnabled && shouldVibrate(keyStates, newStates))
			vibrator.vibrate(33);

		keyStates = newStates;
		gameKeyListener.onGameKeyChanged();
	}

	private int get4WayDirection(float x, float y) {
		x -= 0.5f;
		y -= 0.5f;

		if (Math.abs(x) >= Math.abs(y))
			return (x < 0.0f ? 0 : 2);
		return (y < 0.0f ? 1 : 3);
	}

	private int getDpadStates(float x, float y) {
		if (dpad4Way)
			return DPAD_4WAY[get4WayDirection(x, y)];

		
		float DPAD_DEADZONE = 0.1667f;
		final float cx = 0.5f;
		final float cy = 0.5f;
		int states = 0;

		if (x < cx-DPAD_DEADZONE)
			states |= Emulator.GAMEPAD_LEFT;
		else if (x > cx+DPAD_DEADZONE)
			states |= Emulator.GAMEPAD_RIGHT;
		if (y < cy-DPAD_DEADZONE)
			states |= Emulator.GAMEPAD_UP;
		else if (y > cy+DPAD_DEADZONE)
			states |= Emulator.GAMEPAD_DOWN;

		return states;
	}

	private int getButtonsStates(float x, float y, float size) {
		return BUTTONS_4WAY[get4WayDirection(x, y)];
	}

	private int getSelectStartStates(float x, float y) {
		return (x < 0.5f ? Emulator.GAMEPAD_SELECT : Emulator.GAMEPAD_START);
	}

	private float getEventX(MotionEvent event, int index, boolean flip) {
		float x = Wrapper.MotionEvent_getX(event, index);
		if (flip)
			x = view.getWidth() - x;
		return (x * scaleX);
	}

	private float getEventY(MotionEvent event, int index, boolean flip) {
		float y = Wrapper.MotionEvent_getY(event, index);
		if (flip)
			y = view.getHeight() - y;
		return y * scaleY;
	}

	private Control findControl(float x, float y) {
		Control hit = null;
		int min = 99999;
		for (Control c : controls) {
			int distance = c.hitTest(x, y);
			if (distance<=0) {
				return c;
			} else if (min>distance&&c!=selectStart) {
				min = distance;
				hit = c;
			}
		}
		return hit;
	}

	private int getControlStates(Control c, float x, float y, float size) {
		x = (x - c.getX()) / c.getWidth();
		y = (y - c.getY()) / c.getHeight();

		if (c == dpad)
			return getDpadStates(x, y);
		if (c == buttons)
			return getButtonsStates(x, y, size);
		if (c == selectStart)
			return getSelectStartStates(x, y);
		if (c == leftShoulder)
			return getLeftShoulderStates(x, y);
		if (c == rightShoulder)
			return getRightShoulderStates(x, y);

		return 0;
	}
	
	private int getLeftShoulderStates(float x, float y) {
		return x < 0.6f?Emulator.GAMEPAD_TL:Emulator.GAMEPAD_TL2;
	}
	
	private int getRightShoulderStates(float x, float y) {
		return x > 0.4f?Emulator.GAMEPAD_TR:Emulator.GAMEPAD_TR2;
	}

	public boolean onTouch(MotionEvent event, boolean flip) {
		
		int action = event.getAction();
		int id;
		int pointerUpId = -1;

		switch (action & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
			setKeyStates(0);
			return true;
		case MotionEvent.ACTION_DOWN:
		case MotionEvent.ACTION_POINTER_DOWN:
			id = (action >> 8);
			if (id < touchedControls.length) {
				int index = Wrapper.MotionEvent_findPointerIndex(event, id);
				touchedControls[id] = findControl(getEventX(event, index, flip), getEventY(event, index, flip));
			}
			break;
		case MotionEvent.ACTION_POINTER_UP:
			id = (action >> 8);
			if (id < touchedControls.length) {
				pointerUpId=id;
				touchedControls[id] = null;
			}
			break;
		case MotionEvent.ACTION_MOVE:
		case MotionEvent.ACTION_OUTSIDE:
			break;
		default:
			return false;
		}
		
		int states = 0;
		int n = Wrapper.MotionEvent_getPointerCount(event);
		for (int i=0; i<n;i++){
			id = Wrapper.MotionEvent_getPointerId(event, i);
			if (id!=pointerUpId&&id<touchedControls.length&&touchedControls[id]!=null){
				states |= getControlStates(touchedControls[id], getEventX(event, i, flip), getEventY(event, i, flip), Wrapper.MotionEvent_getSize(event, i));
			}
		}
		setKeyStates(states);
		return true;
	}


	private static class Control {
		private int resId;//
		private boolean hidden;//
		private boolean disabled;//
		private Bitmap bitmap;//
		private RectF bounds = new RectF();//

		Control(int r) {
			resId = r;
		}//

		final float getX() {
			return bounds.left;
		}//

		final float getY() {
			return bounds.top;
		}//

		final int getWidth() {
			return bitmap.getWidth();
		}//

		final int getHeight() {
			return bitmap.getHeight();
		}//

		final boolean isEnabled() {
			return !disabled;
		}//

		final void hide(boolean b) {
			hidden = b;
		}//

		final void disable(boolean b) {
			disabled = b;
		}//

		final int hitTest(float x, float y) {
			if (disabled) {
				return 99999;
			} else {
				float dx = 0.0f;
				float dy = 0.0f;
				if (x < bounds.left){
					dx = bounds.left - x;
				} else if (x>bounds.right) {
					dx = x - bounds.right;
				}
				if (y < bounds.top) {
					dy = bounds.top - y;
				} else if (y > bounds.bottom) {
					dy = y - bounds.bottom;
				}
				return (int)(dx+dy);
			}
		}

		final void move(float x, float y) {
			bounds.set(x, y, x + bitmap.getWidth(), y + bitmap.getHeight());
		}//

		final void load(Resources res, float sx, float sy) {
			bitmap = ((BitmapDrawable) res.getDrawable(resId)).getBitmap();
			bitmap = Bitmap.createScaledBitmap(bitmap,
					(int) (sx * bitmap.getWidth()),
					(int) (sy * bitmap.getHeight()), true);
		}//

		final void reload(Resources res, int id) {	
			int w = bitmap.getWidth();
			int h = bitmap.getHeight();
			bitmap = ((BitmapDrawable) res.getDrawable(id)).getBitmap();
			bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);
		}//

		final void draw(Canvas canvas, Paint paint) {
			if (!hidden && !disabled)
				canvas.drawBitmap(bitmap, bounds.left, bounds.top, paint);
		}//
	}
}
